# -*- coding: utf-8 -*-
import os
import re
import subprocess
import traceback
import make_annotations
import make_csharp_proto


def listdirs(path, depth=0, res=None):
    '''
    遍历文件夹, path 要遍历的路径. depth 文件夹层级, 0 表示当前层
    '''
    if res == None:
        res = list()

    if depth < 0:
        return res

    files = os.listdir(path)
    for filename in files:
        filepath = os.path.join(path, filename)
        if os.path.isdir(filepath):
            depth -= 1
            listdirs(filepath, depth, res)
        else:
            res.append(os.path.normpath(filepath))
    return res


def exec(command: str, input: str = None,
         encoding=None, errors='strict', silent=False) -> str:
    if silent == False:
        print("RUN CMD:", command)
    text_mode = (encoding is None)
    with subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE,
                          universal_newlines=text_mode) as p:
        if input is not None and not text_mode:
            input = input.encode(encoding, errors)  # convert to bytes
        output, err = p.communicate(input)
    if err or p.returncode != 0:
        raise EnvironmentError("Get stderr or exitcode != 0. "
                               "Error: {}, Return Value: {}".format(
                                   ascii(err), p.returncode))
    return output if text_mode else output.decode(encoding, errors)


class chdir():
    def __init__(self, dir):
        self.old_dir = os.path.abspath(os.curdir)
        self.new_dir = os.path.abspath(dir)

    def __enter__(self):
        os.chdir(self.new_dir)

    def __exit__(self, exc_type, exc_val, exc_tb):
        os.chdir(self.old_dir)

class CustomError(Exception):
    def __init__(self, message):
        self.message = message
        super().__init__(self.message)

######################TEMPLATE BEGIN#######################


cmdcode_template = '''\
--- <auto-generated>
---     Generated by the moonfly tool.  DO NOT EDIT!
--- </auto-generated>

local M={
%s
}

local forward = {
%s
}

local mt = { forward = forward }

mt.__newindex = function(_, name, _)
    local msg = "attemp index unknown message: " .. tostring(name)
    error(debug.traceback(msg, 2))
end

mt.__index = function(_, name)
    if name == "forward" then
        return forward
    end
    local msg = "attemp index unknown message: " .. tostring(name)
    error(debug.traceback(msg, 2))
end

return setmetatable(M,mt)
'''

csharp_cmdcode_template = '''\
// <auto-generated>
//     Generated by the moonfly tool.  DO NOT EDIT!
// </auto-generated>

public enum CmdCode
{
%s
}
'''

######################TEMPLATE END#######################

class Config:
    def __init__(self, protoc_file:str, proto_src_dir: str, ignore_file_list: list, special_file_list: list, descriptor_out_file: str, csharp_out_dir: str, csharp_cmd_file: str, cmdcode_out_file: str):
        self.protoc_file = os.path.abspath(protoc_file)
        self.proto_src_dir = os.path.abspath(proto_src_dir)
        self.ignore_file_list = ignore_file_list
        self.special_file_list = special_file_list
        self.descriptor_out_file = os.path.abspath(descriptor_out_file)
        self.csharp_out_dir = os.path.abspath(csharp_out_dir)
        self.cmdcode_out_file = os.path.abspath(cmdcode_out_file)
        self.csharp_cmd_file = os.path.abspath(csharp_cmd_file)


class ProtoGen(object):
    cmd_list = list()
    forward_dict = dict()

    def __init__(self, config: Config):
        self.config = config

    def gen(self):
        files = listdirs(self.config.proto_src_dir, 10)
        pattern = re.compile(r'message\s+([S,C][2,B][S,C]\w+)')

        protofiles = list()

        for filename in files:
            '''解析proto文件'''
            if not filename.endswith(".proto"):
                continue

            name_without_extension = os.path.splitext(
                os.path.basename(filename))[0]
            if name_without_extension in self.config.ignore_file_list:
                print("skip ignored file", filename)
                continue

            with open(filename, 'r', encoding='utf-8') as fobj:
                is_service_message = False
                if name_without_extension in self.config.special_file_list:
                    print("find common proto file", filename)
                else:
                    is_service_message = True
                    print("find service's proto file", filename)

                protofiles.append(filename)
                filecontent = fobj.read()
                match_results = pattern.findall(filecontent)
                if len(match_results) > 0:
                    for one in match_results:
                        if one in self.cmd_list:
                            raise CustomError(
                                "ERROR: message '{0}' already defined!".format(one))
                        self.cmd_list.append(one)
                        if is_service_message:
                            self.forward_dict[one] = name_without_extension

                # fff = os.path.relpath(filename, self.config.proto_src_dir)
                # with chdir(self.config.proto_src_dir):
                #     exec("{0}  --csharp_out=\"{1}\" --proto_path=\"{2}\" {3}".format(
                #         self.config.csharp_protoc_file, self.config.csharp_out_dir, "./", fff))

        tmp = " ".join(protofiles)
        exec("{0} -I{1} -o{2} {3}".format(self.config.protoc_file, self.config.proto_src_dir,
             self.config.descriptor_out_file, tmp))

    def gen_cmdcode(self, startid=1):
        order_list = list[str]()
        try:
            r = re.compile(r'([C,S][2,B][C,S]\w+)')
            with open(self.config.cmdcode_out_file, 'r', encoding='utf-8') as fobj:
                order_list = r.findall(fobj.read())
                unique_list = []
                [unique_list.append(x) for x in order_list if x not in unique_list]
                order_list = unique_list
        except BaseException as _:
            print("can not found old cmd file,will create one")

        for cmd in self.cmd_list:
            if cmd in order_list:
                continue
            order_list.append(cmd)

        rm_list = list()
        for cmd in order_list:
            if cmd in self.cmd_list:
                continue
            rm_list.append(cmd)
            print("Will remove:" + cmd)

        for cmd in rm_list:
            order_list.remove(cmd)

        lua_cmdcode_content = ""
        charp_cmdcode_content = ""
        forward_content = ""

        for cmd in order_list:
            if cmd.startswith("C2S"):
                if cmd in self.forward_dict:
                    forward_content += "    " + cmd + \
                        " = 'addr_" + self.forward_dict[cmd] + "',\n"
            lua_cmdcode_content += "    " + cmd + " = " + str(startid) + ",\n"
            charp_cmdcode_content += "    " + \
                cmd + " = " + str(startid) + ",\n"
            startid += 1

        with open(self.config.cmdcode_out_file, "w", encoding='utf-8') as fobj:
            fobj.write(cmdcode_template % (
                lua_cmdcode_content, forward_content))

        with open(self.config.csharp_cmd_file, "w", encoding='utf-8') as fobj:
            fobj.write(csharp_cmdcode_template % (charp_cmdcode_content))


config = Config(
    protoc_file = "protoc",
    proto_src_dir="../protocol/",
    ignore_file_list=["annotations"],
    special_file_list=["common"],
    descriptor_out_file="../protocol/proto.pb",
    csharp_out_dir="../../BallAction/Assets/Proto",
    csharp_cmd_file="../../BallAction/Assets/Proto/CmdCode.cs",
    cmdcode_out_file="../common/CmdCode.lua"
)

try:
    proto_gen = ProtoGen(config)

    proto_gen.gen()
    proto_gen.gen_cmdcode()


    intelliSense = make_annotations.EmmyLuaIntelliSense()

    protolist, proto_list_with_file = intelliSense.run(
        proto_src_dir= config.proto_src_dir,
        game_dir="../game/",
        game_config_dir="../static/table/",
        json_verify_out_file="../protocol/json_verify.json"
    )

    make_csharp_proto.make_proto(proto_list_with_file, config.csharp_out_dir, config.ignore_file_list)

    print("Execution successful. Press any key to continue.")

except Exception as e:
    traceback.print_exc()

os.system("pause()")
